package javase.jmx.mbeans.dynamic;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.Descriptor;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.ReflectionException;

/*
 * Is not properly synchronized.
 */

public class Dynamic implements DynamicMBean {
	
	private String state;
	
	public Dynamic() {
		// empty constructor
	}
	
	public Dynamic(String state) {
		this.state = state;
	}

	public void printState() {
		System.out.println("Current state is " + state);
	}
	
	public void reset() {
		this.state = null;
	}
	
	public String getState() {
		return state;
	}

	public void setState(String state) {
		System.out.println("setting new value for state");
		this.state = state;
	}

	@Override
	public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
		Object value = null; 
		try {
			value = Dynamic.class.getDeclaredField(attribute).get(this);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			throw new AttributeNotFoundException();
		} catch (SecurityException e) {
			e.printStackTrace();
			throw new ReflectionException(e);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw new ReflectionException(e);
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
			throw new AttributeNotFoundException();
		}
		return value;
	}

	@Override
	public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
		try {
			this.getClass().getDeclaredField(attribute.getName()).set(this, attribute.getValue());
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			throw new AttributeNotFoundException();
		} catch (SecurityException e) {
			e.printStackTrace();
			throw new ReflectionException(e);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw new ReflectionException(e);
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
			throw new AttributeNotFoundException();
		}
	}

	@Override
	public AttributeList getAttributes(String[] attributes) {
		AttributeList result = new AttributeList();
		for (String name : attributes) {
			try {
				result.add(new Attribute(name, getAttribute(name)));
			} catch (Exception e) {
				System.out.println("Can't get attribute " + name);
			}
		}
		return result;
	}

	@Override
	public AttributeList setAttributes(AttributeList attributes) {
		AttributeList changed = new AttributeList();
		for (Object attribute : attributes) {
			try {
				setAttribute((Attribute)attribute);
				changed.add(attribute);
			} catch (Exception e) {
				System.out.println("Can't set attribute " + ((Attribute)attribute).getName());
			}
		}
		return changed;
	}

	@Override
	public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException {
		System.out.println("Invocing " + actionName);
		System.out.println("Signature is " + Arrays.asList(signature));
		Object invocationResult = handleMethodInvocation(actionName, params);
		return invocationResult;
	}

	private Object handleMethodInvocation(String actionName, Object[] params) throws ReflectionException, MBeanException {
		Class<?>[] argTypesArray = getParamsType(params);
		Object invocationResult = null;
		try {
			Method method = this.getClass().getMethod(actionName, argTypesArray);
			invocationResult = method.invoke(this, params);
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
			throw new ReflectionException(e);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			throw new ReflectionException(e);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw new ReflectionException(e);
		} catch (InvocationTargetException e) {
			e.printStackTrace();
			throw new MBeanException(e);
		}
		return invocationResult;
	}

	private Class<?>[] getParamsType(Object[] params) {
		List<Class<?>> argTypes = new ArrayList<Class<?>>();
		for (Object param : params) {
			argTypes.add(param.getClass());
		}
		Class<?>[] argTypesArray = argTypes.toArray(new Class<?>[0]);
		return argTypesArray;
	}

	@Override
	public MBeanInfo getMBeanInfo() {
		String className = this.getClass().getCanonicalName();
		
		String description = "Test dynamic MBean";
		
		MBeanAttributeInfo[] attributes = getAttributesInfo();
		
		MBeanConstructorInfo[] constructors = getBeanConstructorsInfo();
		
		MBeanOperationInfo[] operations = getOperationsInfo();
		
		MBeanNotificationInfo[] notifications = getNotificationsInfo();
		
		MBeanInfo info = new MBeanInfo(className, description, attributes, constructors, operations, notifications);
		
		return info;
	}

	private MBeanAttributeInfo[] getAttributesInfo() {
		boolean isStateWritable = true;
		boolean isStateReadable = true;
		boolean isStateGetterWithSuffixIs = false;
		String stateAttributeName = "state";
		String stateAttributeType = "java.lang.String";
		String stateAttributeDescription = "State attribute";
		MBeanAttributeInfo stateInfo = 
				 new MBeanAttributeInfo(stateAttributeName,
										stateAttributeType, 
										stateAttributeDescription, 
										isStateReadable, 
										isStateWritable, 
										isStateGetterWithSuffixIs);
		
		MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[]{stateInfo};
		return attributes;
	}

	private MBeanConstructorInfo[] getBeanConstructorsInfo() {
		MBeanConstructorInfo emptyParamConstructorInfo = getEmptyParamConstructorInfo();

		MBeanConstructorInfo stringParamConstructorInfo = getStringParamCostructorInfo();

		MBeanConstructorInfo[] result = new MBeanConstructorInfo[]{emptyParamConstructorInfo, stringParamConstructorInfo};
		
		return result;
	}

	private MBeanConstructorInfo getEmptyParamConstructorInfo() {
		String description = "Constructor with no parameters";
		return getConstructorInfo(description);
	}
	
	private MBeanConstructorInfo getStringParamCostructorInfo() {
		String description = "Constructor that establish some state";
		return getConstructorInfo(description, String.class);
	}

	private MBeanConstructorInfo getConstructorInfo(String description, Class<?> ... params) {
		MBeanConstructorInfo constructorInfo = null;
		try {
			Constructor<Dynamic> constructor = Dynamic.class.getConstructor(params);
			constructorInfo = new MBeanConstructorInfo(description, constructor);
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		}
		return constructorInfo;
	}
	
	private MBeanOperationInfo[] getOperationsInfo() {
		
		MBeanOperationInfo printStateBeanOperationInfo = getPringStateOperationInfo();
		
		MBeanOperationInfo resetOperationInfo = getResetOperationInfo();
		
		return new MBeanOperationInfo [] {printStateBeanOperationInfo, resetOperationInfo};
	}

	private MBeanOperationInfo getPringStateOperationInfo() {
		String name = "printState";
		String description = "Prints current state";
		MBeanParameterInfo[] signature = null; // no params
		String type = "void";
		int impact = MBeanOperationInfo.ACTION;
		Descriptor descriptor = null;
		MBeanOperationInfo printStateOperationInfo = new MBeanOperationInfo(name, description, signature, type, impact, descriptor);
		return printStateOperationInfo;
	}
	
	private MBeanOperationInfo getResetOperationInfo() {
		String name = "reset";
		String description = "Resets current state to null";
		MBeanParameterInfo[] signature = null; // no params
		String type = "void";
		int impact = MBeanOperationInfo.ACTION;
		MBeanOperationInfo resetOperationInfo = new MBeanOperationInfo(name, description, signature, type, impact);
		return resetOperationInfo;
	}

	private MBeanNotificationInfo[] getNotificationsInfo() {
		return null;
	}
}
